package com.db.stockexchange.network;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.LinkedList;
import java.util.Queue;

public class MessageTransceiver {
	
	private static final int DEFAULT_BUFFER_SIZE = 1024;
	private static final int LENGTH_PREFIX_BYTES_NUM = 2;
	private static final int MAX_MESSAGE_LENGTH = 65535;
	
	private ByteBuffer readBuffer;
	private Queue<byte[]> messages = new LinkedList<>();
	
	public MessageTransceiver() {
		readBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
	}
	
	public void receive(ReadableByteChannel channel) throws IOException {
		if (channel.read(readBuffer) < 0) {
			// TODO add message
			throw new IOException();
		}
		
		byte[] message;
		while ((message = readSingleMessage()) != null) {
			messages.add(message);
		}
	}
	
	public boolean hasMessage() {
		return !messages.isEmpty();
	}
	
	public byte[] nextMessage() {
		return messages.poll();
	}
	
	public void transmit(byte[] message, WritableByteChannel channel) throws IOException {
		int totalLength = LENGTH_PREFIX_BYTES_NUM + message.length;
		ByteBuffer buffer = ByteBuffer.allocate(totalLength);
		buffer.put(encodeLength(message.length));
		buffer.put(message);
		buffer.flip();
		
		channel.write(buffer);
	}
	
	private byte[] readSingleMessage() {
		readBuffer.flip();
		
		if (readBuffer.remaining() < LENGTH_PREFIX_BYTES_NUM) {
			unflip(readBuffer);
			return null;
		}

		byte[] lengthBytes = new byte[LENGTH_PREFIX_BYTES_NUM];
		readBuffer.get(lengthBytes);
		int messageLength = decodeLength(lengthBytes);

		if (readBuffer.remaining() < messageLength) {
			
			// oops, message is too big for this buffer size
			if (readBuffer.limit() == readBuffer.capacity()) {
				ByteBuffer temp = ByteBuffer.allocate(LENGTH_PREFIX_BYTES_NUM + messageLength);
				readBuffer.position(0);
				temp.put(readBuffer);
				readBuffer = temp;
				return null;
			}
			
			// message received partially, wait for the rest of it
			unflip(readBuffer);
			return null;
		}

		byte[] message = new byte[messageLength];
		readBuffer.get(message);
		readBuffer.compact();

		return message;
	}
	
	private void unflip(ByteBuffer buffer) {
		buffer.position(readBuffer.limit());
		buffer.limit(readBuffer.capacity());
	}
	
	private byte[] encodeLength(int length) {
		if (length <= 0 || length > MAX_MESSAGE_LENGTH) {
			throw new IllegalArgumentException(); // TODO add message
		}
		
		byte[] result = {(byte)((length >>> 8) & 0xFF), (byte)(length & 0xFF)};
		return result;
	}
	
	private int decodeLength(byte[] lengthBytes) {
		if (lengthBytes.length != LENGTH_PREFIX_BYTES_NUM) {
			throw new IllegalArgumentException(); // TODO add message
		}
		
		int result = (lengthBytes[0] << 8) + lengthBytes[1];
		return result;
	}
	
	
	
	
	

}
